using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using XMas;
using SysWinForms = System.Windows.Forms;
using System.IO;       // to avoid conflicts with namespaces


namespace XMasEditor
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class XNAAnimationEditor : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        //SpriteBatch spriteBatch;

        public static XNAAnimationEditor StaticXNAAnimationEditor = null;

        frmEditorMain frmEditor;
        KeyboardState currentKeyboardState = new KeyboardState();
        GamePadState currentGamePadState = new GamePadState();
        float fModelInputFactor = 1.0f;   // Factor depending of model radius, all values tested with 84.0f radius
        
        // Camera stuff
        static float DefaultcameraArc = 0;
        static float DefaultcameraRotation = 0; 
        static float DefaultcameraDistance = 0;
        static Vector3 DefaultcameraPan = new Vector3(0f, 0f, 0f);

        float cameraArc = DefaultcameraArc;
        float cameraRotation = DefaultcameraRotation;
        float cameraDistance = DefaultcameraDistance;
        Vector3 cameraPan = new Vector3(DefaultcameraPan.X, DefaultcameraPan.Y, DefaultcameraPan.Z);


        private Effect _skeletalMeshShader;

        public XNAAnimationEditor()
        {
            StaticXNAAnimationEditor = this;
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            graphics.PreferredBackBufferWidth = 853;
            graphics.PreferredBackBufferHeight = 480;

            graphics.MinimumVertexShaderProfile = ShaderProfile.VS_2_0;
            graphics.MinimumPixelShaderProfile = ShaderProfile.PS_2_0;
        }


        #region XNA.Game

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();

            SysWinForms.Form gameWindowForm = (SysWinForms.Form)SysWinForms.Form.FromHandle(this.Window.Handle);
            gameWindowForm.Shown += new EventHandler(gameWindowForm_Shown);

            this.frmEditor = new frmEditorMain(Content);             
            frmEditor.HandleDestroyed += new EventHandler(myForm_HandleDestroyed);
            frmEditor.WindowState = System.Windows.Forms.FormWindowState.Maximized;
            frmEditor.Show();            
        }
        
        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            //spriteBatch = new SpriteBatch(GraphicsDevice);
            // Load the model.
            RecacheShader();

            //initialModel = Content.Load<Model>("Models/pink/pink");
            //initialModel = Content.Load<Model>("Models/trooper/trooper");
        }

        public void RecacheShader()
        {
            string shaderPath = Directory.GetCurrentDirectory() + "\\Content\\SkinnedModel";
            _skeletalMeshShader = Content.Load<Effect>(shaderPath);
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            HandleInput();
            UpdateCamera(gameTime);

            // Update the animation system
            frmEditor.UpdateAnimationSystem(gameTime.ElapsedGameTime, true, Matrix.Identity);

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {           
            GraphicsDevice device = graphics.GraphicsDevice;

            device.Clear(Color.CornflowerBlue);

            Matrix[] bones = frmEditor.GetAnimationSkinTransforms();

            float aspectRatio = (float)device.Viewport.Width /
                                (float)device.Viewport.Height;
            //float aspectRatio = frmEditor.GetAspectratio();
            //float aspectRatio = 4.0f / 3.0f;

            // Set to zero
            Matrix view = Matrix.Identity;
            Matrix world = Matrix.Identity;
            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                                                                    aspectRatio,
                                                                    1,
                                                                    10000);

            // Render the skinned mesh.
            Model currentModel = frmEditor.CurrentModel;
            // Using model bounding sphere to determine camera
            if (currentModel != null && currentModel.Meshes.Count > 0)
            {                
                BoundingSphere bs = currentModel.Meshes[0].BoundingSphere;
                // Factor depending of model radius, all values tested with 84.0f radius
                fModelInputFactor = bs.Radius / 84.0f;
                Vector3 vCenter = bs.Center;
                Matrix mWorldTransform = frmEditor.GetModelWorldTransform();
                if (mWorldTransform != Matrix.Identity)
                    vCenter = Vector3.Transform(vCenter, mWorldTransform);

                world = Matrix.CreateScale(1.0f) * Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) * Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) * Matrix.CreateRotationZ(0f) * Matrix.CreateTranslation(bs.Center);

                //Vector3 position = vCenter + new Vector3(0, bs.Radius * 0.9f, ((bs.Radius * 2.5f) + cameraDistance)) + cameraPan;
                Vector3 position = vCenter + new Vector3(0, 0, ((bs.Radius * 2.8f) + cameraDistance)) + cameraPan;
                Vector3 vLookAt = vCenter + new Vector3(0, 0, 0) + cameraPan;
                view = Matrix.CreateLookAt(position, vLookAt, Vector3.Up);
            }

            if (currentModel != null)
            {
                _skeletalMeshShader.Parameters["View"].SetValue(view);
                _skeletalMeshShader.Parameters["Projection"].SetValue(projection);
                //_skeletalMeshShader.Parameters["World"].SetValue(world);
                _skeletalMeshShader.Parameters["Bones"].SetValue(bones);
                _skeletalMeshShader.Begin();
                _skeletalMeshShader.CurrentTechnique.Passes[0].Begin();
                foreach (ModelMesh mesh in currentModel.Meshes)
                {
                    foreach (ModelMeshPart meshPart in mesh.MeshParts)
                    {
                        graphics.GraphicsDevice.VertexDeclaration = meshPart.VertexDeclaration;
                        graphics.GraphicsDevice.Vertices[0].SetSource(mesh.VertexBuffer,
                            meshPart.StreamOffset, meshPart.VertexStride);
                        graphics.GraphicsDevice.Indices = mesh.IndexBuffer;
                        graphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList,
                            meshPart.BaseVertex, 0, meshPart.NumVertices,
                            meshPart.StartIndex, meshPart.PrimitiveCount);
                    }
                }
                _skeletalMeshShader.CurrentTechnique.Passes[0].End();
                _skeletalMeshShader.End();
            }

            base.Draw(gameTime);

            this.frmEditor.RefreshPanel();

            // This one will do the trick we are looking for!
            this.GraphicsDevice.Present(this.frmEditor.PanelHandle);
        }

        #endregion


        #region Form

        void myForm_HandleDestroyed(object sender, EventArgs e)
        {
            this.Exit();
        }

        void gameWindowForm_Shown(object sender, EventArgs e)
        {
            ((SysWinForms.Form)sender).Hide();
        }

        #endregion


        #region Handle Input

        /// <summary>
        /// Handles input for quitting the game.
        /// </summary>
        private void HandleInput()
        {
            currentKeyboardState = Keyboard.GetState();
            currentGamePadState = GamePad.GetState(PlayerIndex.One);

            if (currentGamePadState.Buttons.Back == ButtonState.Pressed)
            {
                Exit();
            }
        }

        /// <summary>
        /// Handles camera input.
        /// </summary>
        private void UpdateCamera(GameTime gameTime)
        {
            float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;

            // Check for input to pitch the model up/down
            cameraArc += currentGamePadState.ThumbSticks.Right.Y * time * 0.25f;
            // Limit the arc movement.
            if (cameraArc > 90.0f)
                cameraArc = 90.0f;
            else if (cameraArc < -90.0f)
                cameraArc = -90.0f;

            // Check for input to yaw the model 
            cameraRotation += currentGamePadState.ThumbSticks.Right.X * time * 0.25f;

            // Pan
            cameraPan.X -= currentGamePadState.ThumbSticks.Left.X * time * 0.5f * fModelInputFactor;
            cameraPan.Y -= currentGamePadState.ThumbSticks.Left.Y * time * 0.5f * fModelInputFactor;
            
            // Check for input to zoom camera in and out.           
            cameraDistance += currentGamePadState.Triggers.Left * time * 0.5f * fModelInputFactor;
            cameraDistance -= currentGamePadState.Triggers.Right * time * 0.5f * fModelInputFactor;

            // Limit the camera distance.
            if (cameraDistance > (500.0f * fModelInputFactor))
                cameraDistance = 500.0f * fModelInputFactor;
            else if (cameraDistance < -(fModelInputFactor * 84.0f))    // Our base test is 84
                cameraDistance = -(fModelInputFactor * 84.0f);

            // Default values
            if (currentGamePadState.Buttons.A == ButtonState.Pressed ||
                currentKeyboardState.IsKeyDown(Keys.Escape))
            {
                cameraArc = DefaultcameraArc;
                cameraRotation = DefaultcameraRotation;
                cameraDistance = DefaultcameraDistance;
                cameraPan = new Vector3(DefaultcameraPan.X, DefaultcameraPan.Y, DefaultcameraPan.Z);
            }
        }

        #endregion

    }
}
